import { InjectQueue } from "@nestjs/bullmq";
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { DiscordSnowflake } from "@sapphire/snowflake";
import { Queue } from "bullmq";
import dayjs from "dayjs";
import { catchError, Observable, tap, throwError } from "rxjs";
import { Decorator } from "@/common/enum/decorator.enum";
import { EntitySysLogDto } from "@/modules/sysLog/dto/entity-sys-log.dto";

/** 使用到的日志库对象 */
export const SysLogClassMap = new Map<string, any>();

/** 创建日志拦截器 */
@Injectable()
export class CreateLogInterceptor implements NestInterceptor {
    constructor(
        private readonly reflector: Reflector,
        @InjectQueue("sysLogQueue")
        private readonly sysLogQueue: Queue,
    ) {}

    async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
        const req = context.switchToHttp().getRequest();
        const logMeta = this.reflector.getAllAndOverride(Decorator.CREATE_LOG_KEY, [
            context.getHandler(),
            context.getClass(),
        ]);

        if (!logMeta) {
            return next.handle(); // 没有标记日志的接口, 直接放行
        }

        const { module, action, entity, saveRequestData } = logMeta;
        const actionType = logMeta.type;
        const entityName = entity?.name;
        const requestId = req.body.id || req.params.id;

        /**
         * 踩坑: BullMQ 在将任务数据存入 Redis 时，会自动执行 JSON.stringify()。
         * 只有可序列化的数据（纯 JSON 对象）才能被正确保存
         * 包括下面的 createdAt Date 类型也不会被正确序列化和反序列化
         * 这里缓存实体类，通过 entityName 获取，避免实体类无法序列化的问题
         */
        if (entityName && !SysLogClassMap.has(entityName)) {
            SysLogClassMap.set(entityName, entity);
        }

        const getLogData = (res: any) => {
            const logData: EntitySysLogDto = {
                id: DiscordSnowflake.generate().toString(),
                module,
                type: actionType,
                action,
                operatorId: req.user?.id || null,
                requestUrl: req.originalUrl,
                requestMethod: req.method,
                requestData: saveRequestData ? JSON.stringify(req.body) : null,
                responseData: JSON.stringify(res),
                oldData: null,
                ipAddress: req.ip,
                userAgent: req.headers["user-agent"] || null,
                createdAt: dayjs().toDate(), // 踩坑: 注意这里在传入 redis 时会被序列化为字符串, 接受时需要转换成 Date 类型
            };
            return logData;
        };

        return next.handle().pipe(
            tap((res) => {
                this.sysLogQueue.add("createLogJob", { entityName, requestId, logData: getLogData(res) });
            }),
            catchError((err) => {
                // 发生错误时也记录日志
                const res = { success: false, error: err.message };
                this.sysLogQueue.add("createLogJob", { entityName, requestId, logData: getLogData(res) });
                return throwError(() => err);
            }),
        );
    }
}
